#ifndef TVECTOR_HPP
#define TVECTOR_HPP

#include "type_define.hpp"
#include <cmath>

#pragma region Base tvec element accessor
// base none element accessor
template <typename T, int col>
struct _tvec_base_accessor {};

template <typename T>
struct _tvec_base_accessor<T, 1> {
  union {
    struct {
      T r;
    };
    struct {
      T x;
    };
    T _[1];
  };
  _tvec_base_accessor() { _[0] = T(); }
  _tvec_base_accessor( T val ) { _[0] = val; }
};

template <typename T>
struct _tvec_base_accessor<T, 2> {
  union {
    struct {
      T r, g;
    };
    struct {
      T x, y;
    };
    T _[2];
  };
  _tvec_base_accessor() { _[0] = _[1] = T(); }
  _tvec_base_accessor( T e1, T e2 ) { _[0] = e1, _[1] = e2; }
};

template <typename T>
struct _tvec_base_accessor<T, 3> {
  union {
    struct {
      T r, g, b;
    };
    struct {
      T x, y, z;
    };
    T _[3];
  };
  _tvec_base_accessor() { _[0] = _[1] = _[2] = T(); }
  _tvec_base_accessor( T e1, T e2, T e3 ) { _[0] = e1, _[1] = e2, _[2] = e3; }
};

template <typename T>
struct _tvec_base_accessor<T, 4> {
  union {
    struct {
      T r, g, b, a;
    };
    struct {
      T x, y, z, w;
    };
    T _[4];
  };
  _tvec_base_accessor() { _[0] = _[1] = _[2] = _[3] = T(); }
  _tvec_base_accessor( T e1, T e2, T e3, T e4 ) {
    _[0] = e1, _[1] = e2, _[2] = e3, _[3] = e4;
  }
};

#pragma endregion

/**
 * @brief  base template vector struct
 * @tparam T data type
 * @tparam col vector columns
 * @retval None
 */
template <typename T, ui8 col>
struct tvec : _tvec_base_accessor<T, col> {

  using IndexType = decltype( col );

  tvec() : _tvec_base_accessor() {}

  // deduce the argument pack use c++17 fold-expression
  template <typename... Ts>
  tvec( T a, Ts... args ) {
    constexpr bool value = ( std::is_convertible_v<decltype( args ), T>, ... );
    static_assert( value, "The arg type can not convert to type T" );
    if constexpr ( value ) {
      IndexType i = 0;
      deduce( i++, a );
      ( deduce( i++, static_cast<T>( args ) ), ... );
    }
  }

  tvec( T s ) { LOOP_TVEC( _[i] = s; ) }

  template <typename... Ts, ui8 c>
  tvec( const tvec<T, c> &v, Ts... args ) {
    constexpr bool value = ( std::is_convertible_v<decltype( args ), T>, ... );
    static_assert( value, "The arg type can not convert to type T" );
    if constexpr ( value ) {
      LOOP_TVEC( _[i] = v[i]; if ( i == ( c - 1 ) ) break; )
      ( deduce( c, static_cast<T>( args ) ), ... );
    }
  }

private:
  // assign single argument to the array
  void deduce( IndexType i, T e ) {
    if ( i >= col ) return;
    _[static_cast<ui32>( i++ )] = e;
  }

public:
  /**
   * @brief Index operator
   * @param i the indexer to index the element
   **/
  inline T &operator[]( IndexType i ) {
    return _[i % static_cast<decltype( i )>( col )];
  }

  inline const T &operator[]( IndexType i ) const {
    return _[i % static_cast<decltype( i )>( col )];
  }

  inline tvec operator+( tvec &a ) {
    tvec v;
    LOOP_TVEC( v[i] = _[i] + a[i]; )
    return v;
  }

  inline tvec operator+( const tvec &a ) const {
    tvec v = a;
    LOOP_TVEC( v[i] = _[i] + a[i]; )
    return v;
  }

  inline tvec operator+( T a ) {
    tvec v;
    LOOP_TVEC( v[i] = _[i] + a; )
    return v;
  }

  inline tvec operator-( tvec &a ) {
    tvec v;
    LOOP_TVEC( v[i] = _[i] - a[i]; )
    return v;
  }

  inline tvec operator-( const tvec &a ) const {
    tvec v;
    LOOP_TVEC( v[i] = _[i] - a[i]; )
    return v;
  }

  /**
   * \brief unary substract operator
   * \note
   * \retval None
   */
  inline tvec operator-() {
    tvec v = *this;
    LOOP_TVEC( v[i] = -v[i]; )
    return v;
  }

  template <typename U,
            typename = std::enable_if_t<std::is_convertible_v<U, T>>>
  inline tvec operator*( U s ) {
    tvec v = *this;
    LOOP_TVEC( v[i] = v[i] * static_cast<T>( s ); )
    return v;
  }

  template <typename U,
            typename = std::enable_if_t<std::is_convertible_v<U, T>>>
  inline tvec operator*( U s ) const {
    tvec v = *this;
    LOOP_TVEC( v[i] = v[i] * static_cast<T>( s ); )
    return v;
  }

  template <typename U,
            typename = std::enable_if_t<std::is_convertible_v<U, T>>>
  inline tvec operator/( U s ) {
    tvec v = *this;
    LOOP_TVEC( v[i] = v[i] / static_cast<T>( s ); )
    return v;
  }

  template <typename U,
            typename = std::enable_if_t<std::is_convertible_v<U, T>>>
  inline tvec &operator*=( U s ) {
    LOOP_TVEC( _[i] *= s; )
    return *this;
  }

  template <typename U,
            typename = std::enable_if_t<std::is_convertible_v<U, T>>>
  inline tvec &operator/=( U s ) {
    LOOP_TVEC( _[i] /= s; )
    return *this;
  }

  inline tvec &operator+=( tvec &b ) {
    LOOP_TVEC( _[i] += b[i]; )
    return *this;
  }

  inline tvec &operator-=( tvec &b ) {
    LOOP_TVEC( _[i] -= b[i]; )
    return *this;
  }

  inline T magnitude() { return sqrt( squareLength() ); }

  inline T squareLength() {
    T r = T();
    LOOP_TVEC( r += _[i] * _[i]; )
    return r;
  }

  // normalized vector
  inline void normalized() {
    T len = magnitude();
    LOOP_TVEC( _[i] /= len; )
  }

  inline T minComponent() {
    T tmp = _[0];
    LOOP_TVEC( auto val = _[i]; tmp = val < tmp ? val : tmp; )
    return tmp;
  }

  inline T maxComponent() {
    T tmp = _[0];
    LOOP_TVEC( auto val = _[i]; tmp = val < tmp ? tmp : val; )
    return tmp;
  }

  inline T maxAbsComponent() {
    T tmp = abs( _[0] );
    LOOP_TVEC( auto val = std::abs( _[i] ); tmp = val < tmp ? tmp : val; )
    return tmp;
  }

  inline T minAbsComponent() {
    T tmp = abs( _[0] );
    LOOP_TVEC( auto val = abs( _[i] ); tmp = val < tmp ? val : tmp; )
    return tmp;
  }

  inline ui32 indexOfMinComp() {
    T tmp = _[0];
    ui8 index = 0;
    LOOP_TVEC( auto val = _[i]; tmp = val < tmp ? ( index++, val ) : tmp; )
    return index;
  }

  inline ui32 indexOfMaxComp() {
    T tmp = _[0];
    ui8 index = 0;
    LOOP_TVEC( auto val = _[i]; tmp = val < tmp ? tmp : ( index++, val ); )
    return index;
  }

  inline ui32 indexOfMinAbsComp() {
    T tmp = abs( _[0] );
    ui8 index = 0;
    LOOP_TVEC( auto val = abs( _[i] );
               tmp = val < tmp ? ( index++, val ) : tmp; )
    return index;
  }

  inline ui8 indexOfMaxAbsComp() {
    T tmp = abs( _[0] );
    ui8 index = 0;
    LOOP_TVEC( auto val = abs( _[i] );
               tmp = val < tmp ? tmp : ( index++, val ); )
    return index;
  }

  inline void mulCompWise( const tvec &v ) { LOOP_TVEC( _[i] *= v[i]; ) }
  inline void divCompWise( const tvec &v ) { LOOP_TVEC( _[i] /= v[i]; ) }

  inline bool operator==( tvec &o ) {
    bool r = true;
    LOOP_TVEC( bool t = ( _[i] - o[i] < FLT_EPSILON ); r = r && t; )
    return r;
  }

  inline bool operator!=( tvec &o ) { return !( *this == o ); }

  template <ui8 c>
  inline tvec &operator=( const tvec<T, c> &v ) {
    LOOP_TVEC( _[i] = v[i]; if ( i == ( c - 1 ) ) break; )
    return *this;
  }

  template <typename U>
  inline operator tvec<U, col>() const {
    tvec<U, col> r;
    LOOP_TVEC( r[i] = static_cast<U>( _[i] ); )
    return r;
  }
};

/* ! 1: Const Function issue*/
template <typename T, ui8 col, typename U,
          typename = std::enable_if_t<std::is_convertible_v<U, T>>>
inline tvec<T, col> operator*( U s, const tvec<T, col> &t ) {
  tvec<T, col> v = t;
  LOOP_TVEC( v[i] = v[i] * static_cast<T>( s ); )
  return v;
}

template <typename T, ui8 col, typename U,
          typename = std::enable_if_t<std::is_convertible_v<U, T>>>
inline tvec<T, col> operator+( U s, const tvec<T, col> &t ) {
  tvec<T, col> v = t;
  LOOP_TVEC( v[i] = v[i] + static_cast<T>( s ); )
  return v;
}

#pragma region convient element accessor

#include "vector.inl"

#pragma endregion

#include "print_vec.inl"

#endif